/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework.type;

import axil.api.AxilObject;
import axil.api.Formatted;
import axil.api.Formatting;
import axil.api.Persona;
import axil.definition.UnitDefinition;
import axil.stdlib.numbers.type.Num;


/**
 * A base class providing much of the mechanism for a unitized value.
 */
public abstract class AbstractUnitizedValue<T extends AxilObject & Formatted & UnitizedValue<T>>
	extends Num implements UnitizedValue<T> {
	/**
	 * Tell the magnitude of this numeric value. This is used in all arithmetic
	 * computations to ensure like objects are used.
	 */
	public int kind() {
		return Num.UNITIZED;
	}


	/**
	 * Tell if this number has any significant fractional digits. For example,
	 * the number 3.1 would return true, but 3.0 would not.
	 */
	public boolean fractional() {
		return magnitude().fractional();
	}


	/**
	 * Return a floating point representation of this object. It is understood
	 * the some loos of information may result. Invoking this method is
	 * accepting that side effect.
	 */
	public double fp() {
		return magnitude().fp();
	}


	/**
	 * Get this value as an integer value, discarding any fractional component.
	 * For example, 12.65 returns the integer value 12. No rounding is performed,
	 * only truncation. If the value is greater than what can fit in a 64-bit
	 * integer, then an exception is thrown.
	 */
	public long whole() {
		return magnitude().whole();
	}


	/**
	 * Get the fractional portion of this numeric value as a floating point
	 * value. For example, 12.325 returns a value of 0.325. If there is no
	 * fractional portion, then 0.0 is returned.
	 */
	public double tenths() {
		return magnitude().tenths();
	}


	/**
	 * Return a true value if this number is greater than zero. A value of zero
	 * returns a false value.
	 */
	public boolean positive() {
		return magnitude().positive();
	}


	/**
	 * Convert this object to another object of the same type, but in different
	 * units.
	 */
	public T as(UnitDefinition units) {
		final UnitDefinition u = units();
		if (units == u) {
			return (T)this;
		}
		final UnitDefinition baseline = u.units().baseline();
		Num value = magnitude();
		if (units == baseline) {
            // Converting into units that are the same as the baseline of the original.
			value = value.divide(u.conversion());
		} else {
			if (u == baseline) {
                // Converting from units that are their own baseline
				value = value.multiply(units.conversion());
			} else {
                // Converting between units that should share a common baseline
				Num base = value.divide(u.conversion());
				value = base.multiply(units.conversion());
			}
		}
		return (T)((UnitizedType)this.type()).from(value, units);
	}


	protected T equalize(AxilObject other) {
		T v = (T)other;
		final UnitDefinition u = units();
		if (u != v.units()) {
			v = v.as(u);
		}
		return v;
	}


	/**
	 * Return the value of this object as an object of the indicated size. It
	 * can be safely assumed that the caller intended whatever side effects
	 * occur as part of the conversion are intentional.
	 */
	public Num as(int kind) {
		return magnitude().as(kind);
	}


	/**
	 * Return the host application (or built-in Java) object that most closely
	 * matches this value object. Return returned value is never null unless
	 * this value object represents the special 'nil' object.
	 */
	public Object intrinsic() {
		return magnitude().intrinsic();
	}


	/**
	 * Format this object in a locale-friendly manner. The string returned is
	 * a localized representation of this object using the locale and timezone
	 * associated with this persona. The formatting given is never null and
	 * matches formatting suitable for this type.
	 */
	public String format(Persona persona, Formatting formatting) {
		return magnitude().format(persona, formatting) + units().unicode();
	}


	/**
	 * Return a string representation of this object. The string returned is
	 * never null.
	 */
	public String toString() {
		return magnitude().toString() + units().ascii();
	}
}
